#include "common.hpp"
#include "monitor_aparatos.hpp"

static const char *optString = "c:n:h?";

static const struct option longOpts[] = {
	{ "help", no_argument, NULL, 'h' },
	{ "name", required_argument, NULL, 'n' },
	{ "capacity", required_argument, NULL, 'c' },
	{ 0,0,0,0 }
};

// Funcion que decide si una rutina se puede realizar en un aparato dado.
bool es_compatible(const enum eRutina& rutina, const enum eAparato& aparato) {
	return true;
}

// Delegado para procesar cada aparato en la busqueda de uno disponible para asignar a un cliente.
class buscar_aparato_para_cliente : public procesar_aparato {
	protected:
		entrenador_id _entrenador;
		eRutina _rutina;
		cliente_id _cliente;
		aparato_id encontrado;
	
		virtual bool _do (sAparato& aparato) {
			if ( es_compatible(_rutina, aparato.tipo) ) {
				// Si el aparato no esta pedido por ningun entrenador lo reservo.
				if ( (aparato.entrenador == 0) ) {
//					aparato.usuario = _cliente;
					aparato.entrenador = _entrenador;
					encontrado = aparato.id;
					return false;
				}
			}
			return true;
		}
	public:	
		buscar_aparato_para_cliente(const entrenador_id entrenador = 0, const eRutina& rutina = INDEFINIDA, const cliente_id& cliente = 0)
			: _entrenador(entrenador), _rutina(rutina), _cliente(cliente), encontrado(NIL) {
		}
	
		// Reinicia el asistente para buscar otro aparato.
		void reset(const entrenador_id& entrenador, const enum eRutina& rutina, const cliente_id& cliente) {
			this->_entrenador = entrenador;
			this->_rutina = rutina;
			this->_cliente = cliente;
			this->encontrado = 0;
		}
	
		const aparato_id getEncontrando() const {
			return encontrado;
		}
};

int main (int argc, char* const argv[]) {
	srand( time(NULL) + getpid() );

	int entrenador_id = 0;
	entrenador_id = 1+rand()%MAX_ENTRENADORES;

	// Selecciono por que puerta sale/entra la persona a generar.
	int discriminanteE = rand()%2 +16;
	int discriminanteS = rand()%2 +16;
	ipc::messages_queue_with_exceptions colaE ( ftok(BASE_NAME,discriminanteE) );
	ipc::messages_queue_with_exceptions colaS ( ftok(BASE_NAME,discriminanteS) );
	ipc::messages_queue_with_exceptions colaRecepcion ( ftok(BASE_NAME,18) );
	ipc::messages_queue_with_exceptions colaAtencion ( ftok(BASE_NAME,19) );

	monitor_reservas_with_exceptions reservas(ftok(BASE_NAME,13),ftok(BASE_NAME,12),0);

	monitor_aparatos_with_exceptions aparatos(ftok(BASE_NAME,15), ftok(BASE_NAME,14),0);

	// Asistente para buscar aparatos disponibles.
	buscar_aparato_para_cliente buscador;

	// Parametros.
	std::string nombre;
	size_t clientes = 0;
	size_t capacidad = 0;

	// Variables.
	int longIndex = 0;
	int option = 0;
	bool ayuda = false;

	sMensaje pedir_entrar;
	pedir_entrar.tipo = 1;
	pedir_entrar.origen = getpid();
	pedir_entrar.data = 'E';

	sMensaje registrarse;
	registrarse.tipo = 1;
	registrarse.origen = getpid();
	registrarse.data = 'R';

	sMensaje estoy_ocupado;
	estoy_ocupado.tipo = 1;
	estoy_ocupado.origen = getpid();
	estoy_ocupado.data = 'O';

	sMensaje estoy_libre;
	estoy_libre.tipo = 1;
	estoy_libre.origen = getpid();
	estoy_libre.data = 'L';

	sMensaje pedir_salir;
	pedir_salir.tipo = 1;
	pedir_salir.origen = getpid();
	pedir_salir.data = 'S';

	std::map<cliente_id,size_t> rutinas_asignadas;
	std::map<cliente_id,size_t> tiempo_demorado;

	sMensaje msg;
	sMensaje msg_cliente;
	sRutina rutina;
	
	// Interpretación de parametros.
	while ( (option = getopt_long(argc,argv,optString,longOpts,&longIndex) ) != EOF ) {
		switch (option) {
			case '?':
			case 'h':
			case ':':
				ayuda = true;
				break;
			case 'n':
				nombre = optarg;
				break;
			case 'c':
				capacidad = parse(optarg,0);
			default:
				break;
		}
	}

	if ( ayuda || nombre.empty() || capacidad <= 0) {
		std::cerr << "Proceso que representa un entrenador que trabaja en el gimnasio." << std::endl;
		std::cerr << "\tUso: " << argv[0] << " [-h] <-name string> <-capacity natural>" << std::endl;
		std::cerr << "\tParametros:" << std::endl;
		std::cerr << "\t\t-h,--help:\tMostrar este mensaje de ayuda." << std::endl;
		std::cerr << "\t\t-n,--name:\tDefinir el nombre del entrenador." << std::endl;
		std::cerr << "\t\t-c,--capacity:\tCantidad de aparatos que puede atender a la vez." << std::endl;

		return (nombre.empty()) ? -1 : 0;
	}

	logger.setColor("\033[2;33m");
	try {
		colaE.get(PERMITS);
		colaS.get(PERMITS);
		colaRecepcion.get(PERMITS);
		colaAtencion.get(PERMITS);

		reservas.get(PERMITS);
		aparatos.get(PERMITS);
		
		nombre = "Entrenador " + nombre + "(" + parse(entrenador_id) + ")";
		logger << nombre << ": Inicio." << nl;

		//entrar.
		logger << nombre << ": Intenta entrar por la puerta " << (discriminanteE-15) << " (" << (ftok(BASE_NAME,discriminanteE)) << ")." << nl;

		colaE.send(pedir_entrar);
		msg.data = '\0';
		msg_cliente.data = '\0';
		timeval actual, entrada;
		size_t transcurrido;
		size_t limite_demora = 10*MINUTO; // un cliente puede demorarse hasta 10 minutos (total entre todas sus rutinas).

		colaE.receive(msg,getpid());
		// msg deberia ser la respuesta de pedir_entrar.

		rutina.origen = getpid();

		if (msg.data == 'E') {
			logger << nombre << ": Ingresa al gimnasio." << nl;

			colaRecepcion.send(registrarse);
			msg.data = '\0';
			colaRecepcion.receive(msg,getpid());

			if ( msg.data == 'R' ) {
				// Indicar que es un entrenador disponible.
				gettimeofday(&entrada,NULL);
				transcurrido = 0;
				do {
					logger << nombre << ": Esperando clientes." << nl;
					colaAtencion.receive(msg_cliente,getpid());

					logger << nombre << ": Atendiendo al cliente : " << msg_cliente.origen << "." << nl;

					if (rutinas_asignadas.count(msg_cliente.origen) == 0) {
						rutinas_asignadas[msg_cliente.origen] = 0;
						tiempo_demorado[msg_cliente.origen] = 0;
					}

					rutina.tipo = msg_cliente.origen;
				
					// Si el entrenador considera que el usuario puede seguir entrenando.
					size_t asignadas = rutinas_asignadas[msg_cliente.origen];
					size_t demora = tiempo_demorado[msg_cliente.origen];
					// Simula la decisión del entrenador sobre sí el cliente entreno suficiente.
					size_t limite_asignadas = static_cast<size_t>( (10 + rand()%5) ); // Un cliente puede realizar entre 10 y 15 rutinas.
					logger << nombre << ": Verificando si el cliente termino el entrenamiento (" << asignadas << " < " << limite_asignadas << ")." << nl;
					logger << nombre << ": Espera acumulada del cliente: " << demora << " < " << limite_demora << "." << nl;
					if ( (asignadas < limite_asignadas ) && ( demora < limite_demora ) ) {
						// El entrenador elije una rutina para el cliente.
						logger << nombre << ": Elijiendo una rutina para el cliente." << nl;
						rutina.id = rutina_al_azar();

						// El entrenador busca un aparato disponible que permita realizar la rutina.
						logger << nombre << ": Elijiendo un aparato compatible con la rutina para el cliente." << nl;
						buscador.reset(getpid(),rutina.id,msg_cliente.origen);
						aparatos.for_each_aparato(buscador);

						// En caso de exito se le avisa al cliente que realize la tarea asignada.
						if ( buscador.getEncontrando() != 0 ) {
							logger << nombre << ": Se encontro una combinación disponible." << nl;
							rutina.aparato = buscador.getEncontrando();
							rutina.minutos = calcular_duracion();
						} else {
							logger << nombre << ": No hay un aparato disponible para la rutina." << nl;
						}
					} else {
						rutina.id = SALIR;
						if (rutinas_asignadas[msg_cliente.origen] > 0) {
							if (clientes == capacidad) {
								logger << nombre << ": Puedo aceptar más clientes." << nl;
								colaRecepcion.send(estoy_libre);
								msg.data= '\0';	
								colaRecepcion.receive(msg,getpid());
							}
							clientes--;
						}
					}

					if ( ( rutina.id == SALIR ) || ( buscador.getEncontrando() != 0 ) ) {
						// Envio la rutina al cliente.
						logger << nombre << ": Avisando al cliente " << rutina.tipo << " que rutina realizar." << nl;
						colaAtencion.send(rutina);

						// Registro la asignación de la rutina.
						rutinas_asignadas[rutina.tipo]++;
						// Registro la atención del cliente y aviso en caso de saturación.
						clientes++;
						if (clientes == capacidad) {
							logger << nombre << ": Me sature de trabajo." << nl;
							colaRecepcion.send(estoy_ocupado);
							msg.data = '\0';
							colaRecepcion.receive(msg,getpid());
						}
					} else {
						logger << nombre << ": Demorado junto con cliente " << msg_cliente.origen << " por que no hay aparatos." << nl;
//						if (fork() == 0) {
//							// duermo un tiempo y agrego el token de atención del cliente a la cola de atención.
							usleep(30*SEGUNDO);
							tiempo_demorado[msg_cliente.origen]+=(30*SEGUNDO);
							colaAtencion.send(msg_cliente);
//							exit(0);
//						}
					}

					gettimeofday(&actual,NULL);
					transcurrido = (1E6*(actual.tv_sec - entrada.tv_sec) + (actual.tv_usec - entrada.tv_usec) )/SEGUNDO;

					logger << nombre << ": Atendiendo personas. Faltan " << (MEDIO_TURNO - transcurrido) << " segundos para salir." << nl;
				} while ( transcurrido < MEDIO_TURNO );
			}

			colaRecepcion.send(pedir_salir);
			msg.data = '\0';
			colaRecepcion.receive(msg,getpid());

			logger << nombre << ": Marco su salida en recepción. " << std::boolalpha << (msg.data=='S') << "." << nl;

			//salir.
			logger << nombre << ": Intenta salir por la puerta " << (discriminanteS-15) << " (" << ftok(BASE_NAME,discriminanteS) << ")." << nl;

			colaS.send(pedir_salir);
			msg.data = '\0';
			colaS.receive(msg,getpid());
			// msg deberia ser la respuesta de pedir_salir.
			
			if (msg.data != 'S')
				logger << nombre << ": Error de logica al recibir mensaje de salida del gimnasio." << " D: " << msg.data << nl;
		} else if (msg.data == 'S') {
			logger << nombre << ": El gimnasio esta lleno." << nl;
		} else 
			logger << nombre << ": Error de logica al recibir mensaje de entrada al gimnasio." << nl;


		logger << nombre << ": Fin." << nl;
	} catch (ipc::exception& e) {
		logger << nombre << ": " << e.what() << "." << nl;
		exit(-1);
	}
	
	return 0;
}

